﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
//引入命名空间
/*
 我们都知道，调用WS可以在工程中添加对WS的WEB引用。   
但是，如果我们不想通过添加引用的方式，而是在代码中动态引用该怎么办呢？   
首先，我们该想到WS的实现也是一个类的形式。   
其次，WS在传输过程中是通过WSDL来进行描述的（使用SOAP协议）。   
因此，我们需要获取WS的WSDL描述，并通过该描述来动态生成程序集。   
最后：通过反射来获取新生成的程序集，并调用其方法！   
上述步骤需要引用如下四个名称空间：   
using System.Web.Services.Description; //WS的描述   
//以下几个用于根据描述动态生成代码并动态编译获取程序集   
using System.CodeDom;   
using Microsoft.CSharp;   
using System.CodeDom.Compiler;   
上述几个名称空间中包括如下几个重要的类：   
using System.Web.Services.Description下：   
ServiceDescription //WS描述   
ServiceDescriptionImporter //通过描述生成客户端代理类，特别注意其中的Style   
以下是MSDN对其的描述：   
XML Web services 的接口通常由 Web 服务描述语言 (WSDL) 文件来说明。例如，若要获取有关使用 http://localhost/service.asmx 处公开的 ASP.NET 的 Web 服务的 WSDL 说明，只需导航到 http://localhost/service.asmx?WSDL。使用 ServiceDescriptionImporter 类可以方便地将 WSDL 说明中包含的信息导入到 System.CodeDom.CodeCompileUnit 对象。通过调整 Style 参数的值，可以指示 ServiceDescriptionImporter 实例生成客户端代理类（通过透明调用该类可提供 Web 服务的功能）或生成抽象类（该类封装 Web 服务的功能而不实现该功能）。如果将 Style 属性设置为 Client，则 ServiceDescriptionImporter 生成客户端代理类，通过调用这些类来提供说明的 Web 服务的功能。如果将 Style 属性设置为 Server，则 ServiceDescriptionImporter 实例生成抽象类，这些类表示所说明的 XML Web services 的功能而不进行实现。然后，可以通过编写从这些抽象类继承的类来对其进行实现，并实现相关的方法。  
using System.CodeDom下：   
CodedomUnit //它用于设定动态代码的名称空间，类名等，可以通过ServiceDescriptionImporter.Import()方法将WS的描述代码写入该类，以作动态编译用  
 */
using System.Net;
using System.IO;
using System.Web.Services.Description;
using System.CodeDom;
using System.CodeDom.Compiler;
using Microsoft.CSharp;
using System.Xml;
using System.Collections;
using System.Xml.Serialization;

namespace stwh_Common
{
    /// <summary>
    /// WebService帮助类，提供动态调用WebService服务
    /// </summary>
    public class WebServiceHelper
    {
        /// <summary>
        /// 动态调用WebService服务
        /// </summary>
        /// <param name="url">WebService地址</param>
        /// <param name="methodname">函数名称</param>
        /// <param name="args">传递的参数</param>
        /// <returns></returns>
        public static object InvokeWebService(string url, string methodname, object[] args)
        {
            //这里的namespace是需引用的webservices的命名空间，在这里是写死的，大家可以加一个参数从外面传进来。  
            string @namespace = "stwhCMS";
            try
            {
                //获取WSDL  
                WebClient wc = new WebClient();
                Stream stream = wc.OpenRead(url + "?WSDL");
                ServiceDescription sd = ServiceDescription.Read(stream);
                string classname = sd.Services[0].Name;

                ServiceDescriptionImporter sdi = new ServiceDescriptionImporter();
                //添加WSDL文档。
                sdi.AddServiceDescription(sd, "", "");

                //生成客户端代理类代码  
                CodeCompileUnit ccu = new CodeCompileUnit();
                //设置命名空间
                CodeNamespace cn = new CodeNamespace(@namespace);
                ccu.Namespaces.Add(cn);
                sdi.Import(cn, ccu);

                //设定编译参数  
                CompilerParameters cplist = new CompilerParameters();
                //不生成可执行文件
                cplist.GenerateExecutable = false;
                //在内存中运行
                cplist.GenerateInMemory = true;
                //添加程序集
                cplist.ReferencedAssemblies.Add("System.dll");
                cplist.ReferencedAssemblies.Add("System.XML.dll");
                cplist.ReferencedAssemblies.Add("System.Web.Services.dll");
                cplist.ReferencedAssemblies.Add("System.Data.dll");

                //编译代理类  
                CSharpCodeProvider csc = new CSharpCodeProvider();
                CompilerResults cr = csc.CompileAssemblyFromDom(cplist, ccu);
                if (cr.Errors.HasErrors)
                {
                    //处理错误信息
                    System.Text.StringBuilder sb = new System.Text.StringBuilder();
                    foreach (System.CodeDom.Compiler.CompilerError ce in cr.Errors)
                    {
                        sb.Append(ce.ToString());
                        sb.Append(System.Environment.NewLine);
                    }
                    throw new Exception(sb.ToString());
                }

                //生成代理实例，并调用方法  
                System.Reflection.Assembly assembly = cr.CompiledAssembly;
                Type t = assembly.GetType(@namespace + "." + classname, true, true);
                object obj = Activator.CreateInstance(t);
                System.Reflection.MethodInfo mi = t.GetMethod(methodname);

                return mi.Invoke(obj, args);
            }
            catch
            {
                return null;
            }
        }

        /// <summary>
        /// 动态调用WebService服务
        /// </summary>
        /// <param name="url">WebService地址</param>
        /// <param name="classname">类名称</param>
        /// <param name="methodname">函数名称</param>
        /// <param name="args">传递的参数</param>
        /// <returns></returns>
        public static object InvokeWebService(string url,string classname, string methodname, object[] args)
        {
            //这里的namespace是需引用的webservices的命名空间，在这里是写死的，大家可以加一个参数从外面传进来。  
            string @namespace = "stwhCMS";
            try
            {
                //获取WSDL  
                WebClient wc = new WebClient();
                Stream stream = wc.OpenRead(url + "?WSDL");
                ServiceDescription sd = ServiceDescription.Read(stream);
                if (string.IsNullOrEmpty(classname)) classname = sd.Services[0].Name;

                ServiceDescriptionImporter sdi = new ServiceDescriptionImporter();
                //添加WSDL文档。
                sdi.AddServiceDescription(sd, "", "");

                //生成客户端代理类代码  
                CodeCompileUnit ccu = new CodeCompileUnit();
                //设置命名空间
                CodeNamespace cn = new CodeNamespace(@namespace);
                ccu.Namespaces.Add(cn);
                sdi.Import(cn, ccu);

                //设定编译参数  
                CompilerParameters cplist = new CompilerParameters();
                //不生成可执行文件
                cplist.GenerateExecutable = false;
                //在内存中运行
                cplist.GenerateInMemory = true;
                //添加程序集
                cplist.ReferencedAssemblies.Add("System.dll");
                cplist.ReferencedAssemblies.Add("System.XML.dll");
                cplist.ReferencedAssemblies.Add("System.Web.Services.dll");
                cplist.ReferencedAssemblies.Add("System.Data.dll");

                //编译代理类  
                CSharpCodeProvider csc = new CSharpCodeProvider();
                CompilerResults cr = csc.CompileAssemblyFromDom(cplist, ccu);
                if (cr.Errors.HasErrors)
                {
                    //处理错误信息
                    System.Text.StringBuilder sb = new System.Text.StringBuilder();
                    foreach (System.CodeDom.Compiler.CompilerError ce in cr.Errors)
                    {
                        sb.Append(ce.ToString());
                        sb.Append(System.Environment.NewLine);
                    }
                    throw new Exception(sb.ToString());
                }

                //生成代理实例，并调用方法  
                System.Reflection.Assembly assembly = cr.CompiledAssembly;
                Type t = assembly.GetType(@namespace + "." + classname, true, true);
                object obj = Activator.CreateInstance(t);
                System.Reflection.MethodInfo mi = t.GetMethod(methodname);

                return mi.Invoke(obj, args);
            }
            catch
            {
                return null;
            }
        }

        #region WebService支持Post/Get/SOAP调用

        #region Tip:使用说明
        //webServices 应该支持Get和Post调用，在web.config应该增加以下代码
        //<webServices>
        //  <protocols>
        //    <add name="HttpGet"/>
        //    <add name="HttpPost"/>
        //  </protocols>
        //</webServices>

        //调用示例：
        //Hashtable ht = new Hashtable();  //Hashtable 为webservice所需要的参数集
        //ht.Add("str", "test");
        //ht.Add("b", "true");
        //XmlDocument xx = WebSvcCaller.QuerySoapWebService("http://localhost:81/service.asmx", "HelloWorld", ht);
        //MessageBox.Show(xx.OuterXml);
        #endregion

        /// <summary>
        /// 需要WebService支持Post调用
        /// </summary>
        public static XmlDocument QueryPostWebService(String URL, String MethodName, Hashtable Pars)
        {
            HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(URL + "/" + MethodName);
            request.Method = "POST";
            request.Host = new Uri(URL).Host;
            request.ContentType = "application/x-www-form-urlencoded";
            SetWebRequest(request);
            byte[] data = EncodePars(Pars);
            WriteRequestData(request, data);
            return ReadXmlResponse(request.GetResponse());
        }

        /// <summary>
        /// 需要WebService支持Get调用
        /// </summary>
        public static XmlDocument QueryGetWebService(String URL, String MethodName, Hashtable Pars)
        {
            HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(URL + "/" + MethodName + "?" + ParsToString(Pars));
            request.Method = "GET";
            request.Host = new Uri(URL).Host;
            request.ContentType = "application/x-www-form-urlencoded";
            SetWebRequest(request);
            return ReadXmlResponse(request.GetResponse());
        }

        /// <summary>
        /// 通用WebService调用(Soap),参数Pars为String类型的参数名、参数值
        /// </summary>
        public static XmlDocument QuerySoapWebService(String URL, String MethodName, Hashtable Pars)
        {
            if (_xmlNamespaces.ContainsKey(URL))
            {
                return QuerySoapWebService(URL, MethodName, Pars, _xmlNamespaces[URL].ToString());
            }
            else
            {
                return QuerySoapWebService(URL, MethodName, Pars, GetNamespace(URL));
            }
        }

        private static XmlDocument QuerySoapWebService(String URL, String MethodName, Hashtable Pars, string XmlNs)
        {
            _xmlNamespaces[URL] = XmlNs;//加入缓存，提高效率
            HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(URL);
            request.Method = "POST";
            request.ContentType = "text/xml; charset=utf-8";
            request.Headers.Add("SOAPAction", "\"" + XmlNs + (XmlNs.EndsWith("/") ? "" : "/") + MethodName + "\"");
            SetWebRequest(request);
            byte[] data = EncodeParsToSoap(Pars, XmlNs, MethodName);
            WriteRequestData(request, data);
            XmlDocument doc = new XmlDocument(), doc2 = new XmlDocument();
            doc = ReadXmlResponse(request.GetResponse());

            XmlNamespaceManager mgr = new XmlNamespaceManager(doc.NameTable);
            mgr.AddNamespace("soap", "http://schemas.xmlsoap.org/soap/envelope/");
            String RetXml = doc.SelectSingleNode("//soap:Body/*/*", mgr).InnerXml;
            doc2.LoadXml("<root>" + RetXml + "</root>");
            AddDelaration(doc2);
            return doc2;
        }

        private static string GetNamespace(String URL)
        {
            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(URL + "?WSDL");
            SetWebRequest(request);
            WebResponse response = request.GetResponse();
            StreamReader sr = new StreamReader(response.GetResponseStream(), Encoding.UTF8);
            XmlDocument doc = new XmlDocument();
            doc.LoadXml(sr.ReadToEnd());
            sr.Close();
            return doc.SelectSingleNode("//@targetNamespace").Value;
        }

        private static byte[] EncodeParsToSoap(Hashtable Pars, String XmlNs, String MethodName)
        {
            XmlDocument doc = new XmlDocument();
            doc.LoadXml("<soap:Envelope xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\"></soap:Envelope>");
            AddDelaration(doc);
            //XmlElement soapBody = doc.createElement_x_x("soap", "Body", "http://schemas.xmlsoap.org/soap/envelope/");
            XmlElement soapBody = doc.CreateElement("soap", "Body", "http://schemas.xmlsoap.org/soap/envelope/");
            //XmlElement soapMethod = doc.createElement_x_x(MethodName);
            XmlElement soapMethod = doc.CreateElement(MethodName);
            soapMethod.SetAttribute("xmlns", XmlNs);
            foreach (string k in Pars.Keys)
            {
                //XmlElement soapPar = doc.createElement_x_x(k);
                XmlElement soapPar = doc.CreateElement(k);
                soapPar.InnerXml = ObjectToSoapXml(Pars[k]);
                soapMethod.AppendChild(soapPar);
            }
            soapBody.AppendChild(soapMethod);
            doc.DocumentElement.AppendChild(soapBody);
            return Encoding.UTF8.GetBytes(doc.OuterXml);
        }

        private static string ObjectToSoapXml(object o)
        {
            XmlSerializer mySerializer = new XmlSerializer(o.GetType());
            MemoryStream ms = new MemoryStream();
            mySerializer.Serialize(ms, o);
            XmlDocument doc = new XmlDocument();
            doc.LoadXml(Encoding.UTF8.GetString(ms.ToArray()));
            if (doc.DocumentElement != null)
            {
                return doc.DocumentElement.InnerXml;
            }
            else
            {
                return o.ToString();
            }
        }

        /// <summary>
        /// 设置凭证与超时时间
        /// </summary>
        /// <param name="request"></param>
        private static void SetWebRequest(HttpWebRequest request)
        {
            request.Credentials = CredentialCache.DefaultCredentials;
            request.Timeout = 30000;
        }

        private static void WriteRequestData(HttpWebRequest request, byte[] data)
        {
            request.ContentLength = data.Length;
            Stream writer = request.GetRequestStream();
            writer.Write(data, 0, data.Length);
            writer.Close();
        }

        private static byte[] EncodePars(Hashtable Pars)
        {
            return Encoding.UTF8.GetBytes(ParsToString(Pars));
        }

        private static String ParsToString(Hashtable Pars)
        {
            StringBuilder sb = new StringBuilder();
            foreach (string k in Pars.Keys)
            {
                if (sb.Length > 0)
                {
                    sb.Append("&");
                }
                //sb.Append(HttpUtility.UrlEncode(k) + "=" + HttpUtility.UrlEncode(Pars[k].ToString()));
            }
            return sb.ToString();
        }

        private static XmlDocument ReadXmlResponse(WebResponse response)
        {
            StreamReader sr = new StreamReader(response.GetResponseStream(), Encoding.UTF8);
            String retXml = sr.ReadToEnd();
            sr.Close();
            XmlDocument doc = new XmlDocument();
            doc.LoadXml(retXml);
            return doc;
        }

        private static void AddDelaration(XmlDocument doc)
        {
            XmlDeclaration decl = doc.CreateXmlDeclaration("1.0", "utf-8", null);
            doc.InsertBefore(decl, doc.DocumentElement);
        }

        private static Hashtable _xmlNamespaces = new Hashtable();//缓存xmlNamespace，避免重复调用GetNamespace
        #endregion
    }
}
